#/***************************************************************************
# *   Copyright (c) Victor Titov (DeepSOIC)                                 *
# *                                           (vv.titov@gmail.com) 2016     *
# *                                                                         *
# *   This file is part of the FreeCAD CAx development system.              *
# *                                                                         *
# *   This library is free software; you can redistribute it and/or         *
# *   modify it under the terms of the GNU Library General Public           *
# *   License as published by the Free Software Foundation; either          *
# *   version 2 of the License, or (at your option) any later version.      *
# *                                                                         *
# *   This library  is distributed in the hope that it will be useful,      *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with this library; see the file COPYING.LIB. If not,    *
# *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
# *   Suite 330, Boston, MA  02111-1307, USA                                *
# *                                                                         *
# ***************************************************************************/

__title__="BOPTools.JoinAPI module"
__author__ = "DeepSOIC"
__url__ = "http://www.freecadweb.org"
__doc__ = "JoinFeatures functions that operate on shapes."

import Part
from . import ShapeMerge
from . import generalFuseIsAvailable
from .GeneralFuseResult import GeneralFuseResult
from .Utils import compoundLeaves

def shapeOfMaxSize(list_of_shapes):
    """shapeOfMaxSize(list_of_shapes): finds the shape that has the largest mass in the list and returns it. The shapes in the list must be of same dimension."""
    #first, check if shapes can be compared by size
    ShapeMerge.dimensionOfShapes(list_of_shapes)

    rel_precision = 1e-8

    #find it!
    max_size = -1e100 # max size encountered so far
    count_max = 0 # number of shapes with size equal to max_size
    shape_max = None # shape of max_size
    for sh in list_of_shapes:
        v = abs(Part.cast_to_shape(sh).Mass)
        if v > max_size*(1 + rel_precision) :
            max_size = v
            shape_max = sh
            count_max = 1
        elif (1-rel_precision) * max_size <= v and v <= (1+rel_precision) * max_size :
            count_max = count_max + 1
    if count_max > 1 :
        raise ValueError("There is more than one largest piece!")
    return shape_max

def connect(list_of_shapes, tolerance = 0.0):
    """connect(list_of_shapes, tolerance = 0.0): connects solids (walled objects), shells and
    wires by throwing off small parts that result when splitting them at intersections.

    Compounds in list_of_shapes are automatically exploded, so self-intersecting compounds
    are valid for connect."""

    # explode all compounds before GFA.
    new_list_of_shapes = []
    for sh in list_of_shapes:
        new_list_of_shapes.extend( compoundLeaves(sh) )
    list_of_shapes = new_list_of_shapes

    #test if shapes are compatible for connecting
    dim = ShapeMerge.dimensionOfShapes(list_of_shapes)
    if dim == 0:
        raise TypeError("Cannot connect vertices!")

    if len(list_of_shapes) < 2:
        return Part.makeCompound(list_of_shapes)

    if not generalFuseIsAvailable(): #fallback to legacy
        result = list_of_shapes[0]
        for i in range(1, len(list_of_shapes)):
            result = connect_legacy(result, list_of_shapes[i], tolerance)
        return result

    pieces, map = list_of_shapes[0].generalFuse(list_of_shapes[1:], tolerance)
    ao = GeneralFuseResult(list_of_shapes, (pieces, map))
    ao.splitAggregates()
    #print len(ao.pieces)," pieces total"

    keepers = []
    all_danglers = [] # debug

    #add all biggest dangling pieces
    for src in ao.source_shapes:
        danglers = [piece for piece in ao.piecesFromSource(src) if len(ao.sourcesOfPiece(piece)) == 1]
        all_danglers.extend(danglers)
        largest = shapeOfMaxSize(danglers)
        if largest is not None:
            keepers.append(largest)

    touch_test_list = Part.Compound(keepers)
    #add all intersection pieces that touch danglers, triple intersection pieces that touch duals, and so on
    for ii in range(2, ao.largestOverlapCount()+1):
        list_ii_pieces = [piece for piece in ao.pieces if len(ao.sourcesOfPiece(piece)) == ii]
        keepers_2_add = []
        for piece in list_ii_pieces:
            if ShapeMerge.isConnected(piece, touch_test_list):
                keepers_2_add.append(piece)
        if len(keepers_2_add) == 0:
            break
        keepers.extend(keepers_2_add)
        touch_test_list = Part.Compound(keepers_2_add)


    #merge, and we are done!
    #print len(keepers)," pieces to keep"
    return ShapeMerge.mergeShapes(keepers)

def connect_legacy(shape1, shape2, tolerance = 0.0):
    """connect_legacy(shape1, shape2, tolerance = 0.0): alternative implementation of
    connect, without use of generalFuse. Slow. Provided for backwards compatibility, and
    for older OCC."""

    if tolerance>0.0:
        import FreeCAD as App
        App.Console.PrintWarning("connect_legacy does not support tolerance (yet).\n")
    cut1 = shape1.cut(shape2)
    cut1 = shapeOfMaxSize(cut1.childShapes())
    cut2 = shape2.cut(shape1)
    cut2 = shapeOfMaxSize(cut2.childShapes())
    return cut1.multiFuse([cut2, shape2.common(shape1)])

#def embed(shape_base, shape_tool, tolerance = 0.0):
#    (TODO)

def embed_legacy(shape_base, shape_tool, tolerance = 0.0):
    """embed_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
    embed, without use of generalFuse. Slow. Provided for backwards compatibility, and
    for older OCC."""
    if tolerance>0.0:
        import FreeCAD as App
        App.Console.PrintWarning("embed_legacy does not support tolerance (yet).\n")

    # using legacy implementation, except adding support for shells
    pieces = compoundLeaves(shape_base.cut(shape_tool))
    piece = shapeOfMaxSize(pieces)
    result = piece.fuse(shape_tool)
    dim = ShapeMerge.dimensionOfShapes(pieces)
    if dim == 2:
        # fusing shells returns shells that are still split. Reassemble them
        result = ShapeMerge.mergeShapes(result.Faces)
    elif dim == 1:
        result = ShapeMerge.mergeShapes(result.Edges)
    return result

def cutout_legacy(shape_base, shape_tool, tolerance = 0.0):
    """cutout_legacy(shape_base, shape_tool, tolerance = 0.0): alternative implementation of
    cutout, without use of generalFuse. Slow. Provided for backwards compatibility, and
    for older OCC."""

    if tolerance>0.0:
        import FreeCAD as App
        App.Console.PrintWarning("cutout_legacy does not support tolerance (yet).\n")
    #if base is multi-piece, work on per-piece basis
    shapes_base = compoundLeaves(shape_base)
    if len(shapes_base) > 1:
        result = []
        for sh in shapes_base:
            result.append(cutout(sh, shape_tool))
        return Part.Compound(result)

    shape_base = shapes_base[0]
    pieces = compoundLeaves(shape_base.cut(shape_tool))
    return shapeOfMaxSize(pieces)
